<?php

/**
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; version 3 of the License.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
 */
/**
 * Shopping cart class
 * @package Util
 * @author Somlyai Dezső
 * @author Vincze Tamás pilou@pilou.hu
 * @version 0.4.3
 */

namespace Util\Cart;

class Cart {

	/**
	 * Array of item objects
	 * @var array
	 */
	protected $_items = array();
	
	/**
	 * Do cahce or not
	 * @var bool
	 */
	protected $_cache = false;
	
	/**
	 * The cache time
	 * @var int seconds
	 */
	protected $_cacheTimeOut = 86400;

	/**
	 * Constructor for Cart Util
	 */
	private function __construct() {
		$this->clear(false);
		$this->_cacheGet();
	}

	/**
	 * Factory method for Cart Util
	 * @access public static
	 * @param string $name name of the session
	 * @return Cart
	 */
	public static function get($name = "cart") {
		if (!isset($_SESSION[$name]) || !($_SESSION[$name] instanceof Cart)) {
			$_SESSION[$name] = new static();
		}
		return $_SESSION[$name];
	}
	
    /**
     * Gets array of cart items, can filter by types (instanceof)
     * @param string|array $className className string, or an array of class names
     * @return type 
     */
	public function getAll($classNames = "ICartItem") {
        $return = array();
		
		settype($classNames, "array");
		foreach ($this->_items as $key => $item) {
			foreach ($classNames as $className) {
				$fixedClassName = "Util\Cart\\".$className;
				if ($item instanceof $fixedClassName){
					$return[$key] = $item;
				}
			}
		}
		return $return;
	}

	/**
	 * Get all Cart items
	 * @access public
	 * @return array
	 */
	public function getItems() {
		return $this->getAll("Item");
	}

	/**
	 * Get Shipping
	 * @access public
	 * @return array
	 */
	public function getShipping() {
		$items = $this->getAll("Shipping");
        if (empty($items)){
    		return null;
        }
		return current($items);
	}    

	/**
	 * Add item to Cart
	 * @access public
	 * @param ICartItem $cartItem Cart item oject
	 * @return self
	 */
	public function add(ICartItem $cartItem) {
		$items = $this->getAll();
		foreach ($items as $key => $item) {
			if ($item->getID(false) == $cartItem->getID(false)){
				$this->_items[$key] = $cartItem;
				$doNotAdd = true;
				break;
			}
		}
		if (!isset($doNotAdd)){
			$this->_items[] = $cartItem;
		}
		return $this;
	}

	/**
	 * Update Cart item
	 * @access public
	 * @param int $key
	 * @param string $name Name of Cart item
	 * @param float $price Price of Cart item
	 * @param float $vat Vat of Catr item
	 * @param int $pcs Pcs (qty) of Cart item
	 * @return void
	 */
	public function update($key, $name, $netPrice, $vat, $qty = 1, $misc = null) {
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at add() (".$key.")");
		}
		return $this->_items[$key]->setName($name)->setNetPrice($netPrice)->setVat($vat)->setQty($qty)->setMisc($misc);
	}

	/**
	 * Clear Cart (remove items)
	 * @access public
	 * @var array|string|null $classNames Item classes to be removed (null will remove all items)
	 * @var bool $deleteCache
	 * @return void
	 */
	public function clear($classNames = null, $deleteCache = false) {
		if (is_null($classNames)){
			$this->_items = array();
		} else {
			settype($classNames, "array");
			foreach ($this->_items as $key => $item){
				foreach ($classNames as $className) {
					$fixedClassName = "Util\Cart\\".$className;
					if ($item instanceof $fixedClassName){
						unset($this->_items[$key]);
					}
				}
			}
		}
		if ($deleteCache) {
			$this->_cacheDel();
		}
	}

	/**
	 * Remove a Cart item by key
	 * @param int $key
	 * @return void
	 */
	public function remove($key) {
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at remove() (".$key.")");
		}
		unset($this->_items[$key]);
	}

	/**
	 * Encode the array got by Cart's getAll function
	 * @access public
	 * @see $this->getAll()
	 * @return string
	 */
	public function toString() {
		return base64_encode(serialize($this->getAll()));
	}

	/**
	 * Decode string to fill the Cart with previously serialized object Cart items
	 * @access public
	 * @param string $string
	 * @throws Exception
	 * @return void
	 */
	public function fromString($string) {
		$cart = unserialize(base64_decode($string));
		if ($cart === false) {
			throw new \Exception("Cart Util fromString function failed");
		}
		$this->clear();
		$this->_items = $cart;
	}

	/**
	 * Returns cart item pcs
	 * @access public
	 * @param bool $onlyItems Get only real items
	 * @example sumPcs() get only real item pcs sum count
	 * @example sumPcs(false) get all cart items pcs sum count
	 * @return int
	 */
	public function sumQty($onlyItems = true) {
		$count = 0;
		$items = ($onlyItems) ? $this->getItems() : $this->getAll();
		foreach ($items as $item) {
			$count += $item->getQty();
		}
		return $count;
	}

	/**
	 * Sum net price of Cart items
	 * @access public
	 * @param bool $onlyItems Get only real items
	 * @return mixed integer or float
	 */
	public function sumNet($onlyItems = false) {
		$price = 0;
		$items = ($onlyItems) ? $this->getItems() : $this->getAll();
		foreach ($items as $item) {
			$price += $item->getNetSum();
		}
		return $price;
	}

	/**
	 * Sum gross price of Cart items
	 * @access public
	 * @param bool $onlyItems Get only real items
	 * @return mixed integer or float
	 */
	public function sumGross($onlyItems = false) {
		$price = 0;
		$items = ($onlyItems) ? $this->getItems() : $this->getAll();
		foreach ($items as $item) {
			$price += $item->getGrossSum();
		}
		return $price;
	}

    /**
     * Adds 1 of the quantity of the cart item
     * @access protected
     * @param int $key key of the cart item in $_intems array, usually as POST key from HTTP
     * @throws \Exception if $key is not present
     * @return array of cart items
     */
	public function getCartItem($key) {
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at getCartItem() (".$key.")");
		}
		return $this->_items[$key];
	}

    /**
     * Adds 1 of the quantity of the cart item
     * @access protected
     * @param int $key key of the cart item in $_intems array, usually as POST key from HTTP
     * @throws \Exception if $key is not present
     * @return Cart
     */
	public function addOne($key){
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at addOne() (".$key.")");
		}
		$qty = $this->_items[$key]->getQty();
		$this->updateQty($key, $qty + 1);
        return $this;
	}
    
    /**
     * Substracts 1 of the quantity of the cart item
     * @access protected
     * @param int $key key of the cart item in $_intems array, usually as POST key from HTTP
     * @throws \Exception if $key is not present
     * @return Cart
     */
	public function remOne($key){
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at remOne() (".$key.")");
		}
		$qty = $this->_items[$key]->getQty();
		$this->updateQty($key, $qty - 1);
        return $this;
	}
	
    /**
     * Updates the quantity of the cart item
     * @access protected
     * @param int $key key of the cart item in $_intems array, usually as POST key from HTTP
     * @param int $qty
     * @throws \Exception if $key is not present
     * @return Cart
     */
	public function updateQty($key, $qty) {
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at updateQty() (".$key.")");
		}
		if ($qty > 0) {
			$this->_items[$key]->setQty($qty);
		} else {
			$this->remove($key);
		}
        return $this;
	}

    /**
     * Updates the net price of the cart item
     * @access protected
     * @param int $key key of the cart item in $_intems array, usually as POST key from HTTP
     * @param float $price
     * @throws \Exception if $key is not present
     * @return Cart
     */
	public function updatePrice($key, $price) {
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at updatePrice() (".$key.")");
		}
		$this->_items[$key]->setNetPrice($price);
        return $this;
	}

    /**
     * Updates the name of the cart item
     * @access protected
     * @param int $key key of the cart item in $_intems array, usually as POST key from HTTP
     * @param string $name
     * @throws \Exception if $key is not present
     * @return Cart
     */
	public function updateName($key, $name) {
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at updatePrice() (".$key.")");
		}
		$this->_items[$key]->setName($name);
        return $this;
	}

    /**
     * Updates the Vat of the cart item
     * @access protected
     * @param int $key key of the cart item in $_intems array, usually as POST key from HTTP
     * @param int $vat 
     * @throws \Exception if $key is not present
     * @return Cart
     */
	public function updateVat($key, $vat) {
		if (!isset($this->_items[$key])){
			throw new \Exception("Cart key not found at updatePrice() (".$key.")");
		}
		$this->_items[$key]->setVat($vat);
        return $this;
	}

    /**
     * Get cache file, and load it into the cart if caching is enabled
     * @access protected
     * @return void
     */
	protected function _cacheGet() {
		if ($this->_cache) {
			$id = \Core\HTTP\HTTP::cookie(str_replace(".", "", $_SERVER["HTTP_HOST"]) . "cart");
			if (!is_null($id)) {
				$cache = \Util\Cache::init($id, "cart");
				if ($cache->is($this->_cacheTimeOut)) {
					$this->fromString($cache->get());
				}
			}
		}
	}

    /**
     * Delete cache file if caching is enabled
     * @access protected
     * @return void
     */
	protected function _cacheDel() {
		if ($this->_cache) {
			$id = \Core\HTTP\HTTP::cookie(str_replace(".", "", $_SERVER["HTTP_HOST"]) . "cart");
			if (!is_null($id)) {
				$cache = \Util\Cache::init($id, "cart");
				$cache->del();
			}
		}
	}

    /**
     * Writes cache to file if caching is enabled
     * @access protected
     * @return void
     */
	protected function _cacheSet() {
		if ($this->_cache) {
			$cookieName = str_replace(".", "", $_SERVER["HTTP_HOST"]) . "cart";
			$id = \Core\HTTP\HTTP::cookie($cookieName);
			if (is_null($id)) {
				$id = md5(microtime());
				$expire = mktime(date("H"), date("i"), date("s"), date("m") + 1, date("d"), date("y"));
				\Core\HTTP\HTTP::cookie($cookieName, $id, $expire);
			}
			$cache = \Util\Cache::init($id, "cart");
			if (count($this->getAll()) > 0) {
				$cache->save($this->toString());
			} else {
				$cache->del();
			}
		}
	}

    /**
     * Writes cache to file at the end of the php run if caching is enabled
     * @access public
     * @return void
     */
	public function __destruct() {
		$this->_cacheSet();
	}
   
	/**
	 * Gets if the cart is empty or not
	 * @access public
	 * @return bool
	 */
	public function isEmpty(){
		return empty($this->_items);
	}

}

?>
